﻿//--------------------------------------
// Copyright     : W.A.I.S. TECHNOLOGY 2003-2008 ; http://www.wais-fr.com ; email: infos@wais-fr.com
// Author        : Henri d'Orgeval
// Contributors  : 
// Last release  : 2007/11/09 (yy/mm/dd)
// Version       : 1.0.0.2
// Pattern       : "Real Time Trace"
// Internal name : RealTimeTrace Info
// Description   : this class is a wrapper to manage parameters passed to methods at run time
//                 
// Framework     : v2.0.50727 and above
//
// Licence       : Microsoft Permisse Licence
// W.A.I.S provides this code in the hope that it will be useful, but WITHOUT ANY WARRANTY , either express or implied, including but not limited to the implied warranties of merchantability and/or fitness for a particular purpose.          
// This library is free software; you can redistribute it and/or modify it under the terms of the Microsoft Permissive Licence
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//---------------------------------------

using System;
using System.Collections.Generic;
//using System.Linq;
using System.Text;

namespace RealtimeTrace
{
    /// <summary>
    /// Class to handle all the context at runtime when an exception object is raised by the CLR.
    /// </summary>
    sealed internal class TraceInfo : Itraceable, ITraceInfo
    {

        #region Itraceable Members

        /// <summary>
        /// Use this method to initialize the RealTimeTrace.
        /// This method should be called within the class constructor(s) 
        /// if you want to override the default RealtimeTrace configuration.
        /// </summary>
        private void InitializeRealTimeTrace()
        {
            this.RealTimeTraceProvider = DataProviderType.None;
            this.PreferredDumpType = DumpType.XML;
            this.ErrorsMaxSize = 4096;
        }

        /// <summary>
        /// By default, all exceptions that are raised within the RealtimeTrace layer
        /// are not saved by the Data Storage provider.
        /// </summary>
        private DataProviderType _RealTimeTraceProvider = DataProviderType.None;
        /// <summary>
        /// Gets or Sets the Data Store provider for the RealTimeTrace.
        /// By default this property returns 'LogFile'
        /// </summary>
        public DataProviderType RealTimeTraceProvider
        {
            get
            {
                return this._RealTimeTraceProvider;
            }
            set
            {
                this._RealTimeTraceProvider = value;
            }
        }

        private DumpType _PreferredDumpType = DumpType.XML;
        public DumpType PreferredDumpType
        {
            get
            {
                return this._PreferredDumpType;
            }
            set
            {
                this._PreferredDumpType = value;
            }
        }

        /// <summary>
        /// Sets any internal error message that occured within the execution of the RealTimeTrace Layer.
        /// The error message can be read by the implementor through the LastError property.
        /// </summary>
        /// <returns></returns>
        private string InternalError
        {
            set
            {
                try
                {
                    if (System.String.IsNullOrEmpty(value))
                    {
                        return;
                    }
                    this._LastError = value;
                    this._Errors = value;
                    this._HasErrors = true;
                }
                catch (Exception)
                {
                    // this code should never run, but who knows ...
                }
            }
        }

        private bool _HasErrors = false;
        public bool HasErrors
        {
            get { return this._HasErrors; }
            set { this._HasErrors = value; }
        }

        private System.String _LastError = System.String.Empty;
        public string LastError
        {
            get { return this._LastError; }
            set
            {
                if (value == null)
                {
                    value = System.String.Empty;
                }
                this._LastError = value;
            }
        }

        private System.String _Errors = System.String.Empty;
        public string Errors
        {
            get { return this._Errors; }
            set
            {
                if (value == null)
                {
                    value = System.String.Empty;
                }
                this._Errors = value;
            }
        }

        /// <summary>
        /// Clear any previous error messages hold by the property 'LastError'.
        /// The 'HasErrors' property is reset to false.
        /// Call this method at the beginning of all public methods in your class.
        /// </summary>
        private void ClearErrors()
        {
            try
            {
                this._Errors = System.String.Empty;
                this._LastError = System.String.Empty;
                this._HasErrors = false;
            }
            catch (Exception)
            {
            }
        }

        /// <summary>
        /// Add an error message to the current content of the 'Errors' Property.
        /// The 'HasErrors' property is set to true.
        /// </summary>
        /// <param name="errorMessage">Any message to be added to the Errors Property</param>
        /// <returns></returns>
        /// <remarks>Be carefull: by calling this method many times without calling the 'ClearErrors' in between,
        /// you might create a memory leak in your application.</remarks>
        private bool AddError(string errorMessage)
        {
            try
            {
                if (System.String.IsNullOrEmpty(errorMessage))
                {
                    return true;
                }

                this._HasErrors = true;

                // Update the LastError message
                this._LastError = errorMessage;

                // Update the Errors message
                this._Errors += errorMessage;
                this._Errors += "\r\n";

                // check for the constraint on this._Errors string length.
                // the purpose of this check is to avoid a possible memory leak.
                if (this._Errors.Length > this._ErrorsMaxSize)
                {
                    this._Errors = errorMessage;
                }

                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// Set the default maximum length of the Errors property to 4096 bytes.
        /// This threshold should prevent memory leak due to exceptions storms.
        /// </summary>
        private int _ErrorsMaxSize = 4096;
        /// <summary>
        /// Gets or Sets the maximum length in bytes for the 'Errors' property.
        /// Default value is 4096 bytes.
        /// </summary>
        private int ErrorsMaxSize
        {
            get
            {
                return this._ErrorsMaxSize;
            }
            set
            {

                if (value > 0)
                {
                    this._ErrorsMaxSize = value;
                }
            }
        }

        /// <summary>
        /// Log into the trace data store the error message.
        /// </summary>
        /// <param name="errorMessage">Any error message to be logged.</param>
        private void RealTimeTrace(string errorMessage)
        {
            try
            {
                if (System.String.IsNullOrEmpty(errorMessage))
                {
                    return;
                }

                if (!this.AddError(errorMessage))
                {
                    // something goes wrong ...     
                }
            }
            catch (Exception ex)
            {

                // this code should never run, but who knows ...
                this.InternalError = ex.Message;
            }

        }

        /// <summary>
        /// Log into the trace data store the current exception.
        /// </summary>
        /// <param name="ex">The current exception object to be analyzed and logged for further analysis.</param>
        private void RealTimeTrace(Exception ex)
        {
            try
            {
                if (ex == null)
                {
                    return;
                }

                if (!this.AddError(ex.Message))
                {
                    // something goes wrong ...     
                }
            }
            catch (Exception ex1)
            {
                // this code should never run, but who knows ...
                this.InternalError = ex1.Message;
            }
        }

        /// <summary>
        /// Log into the trace data store the current exception.
        /// </summary>
        /// <param name="ex">The current exception object to be analyzed and logged.</param>
        /// <param name="args">Any number of objects that represent the calling parameters of the method in which an exception has been thrown.</param>
        private void RealTimeTrace(Exception ex, params object[] args)
        {
            try
            {
                if (ex == null)
                {
                    return;
                }

                if (!this.AddError(ex.Message))
                {
                    // something goes wrong ...     
                }
            }
            catch (Exception ex1)
            {
                // this code should never run, but who knows ...
                this.InternalError = ex1.Message;
            }
        }


        #endregion

        #region ITraceInfo Members

        private System.Int32 _Count = -1;
        /// <summary>
        /// Gets the instance counter of this current object.
        /// </summary>
        public System.Int32 Count {
            get
            {
                return this._Count;
            }
        }

        private string _ClassName = System.String.Empty;
        /// <summary>
        /// Gets the name of the Class in which the exception has occured.
        /// </summary>
        public string ClassName
        {
            get { return this._ClassName; }
        }

        private string _MethodName = System.String.Empty;
        /// <summary>
        /// Gets the name of the Method in which the exception has occured.
        /// </summary>
        public string MethodName
        {
            get { return this._MethodName; }
        }


        private List<ITraceParamInfo> _ParamInfoCollection;
        /// <summary>
        /// Gets the collection of parameters passed to the method in which the exception occured.
        /// </summary>
        public List<ITraceParamInfo> ParamInfoCollection
        {
            get { return this._ParamInfoCollection; }
        }

        /// <summary>
        /// Initialize the internal collection _ParamInfoCollection
        /// </summary>
        /// <returns>Returns true if execution of the method has succeeded.
        /// Returns false otherwise.</returns>
        private bool InitParamInfoCollection()
        {
            try
            {
                if (this._ParamInfoCollection == null)
                {
                    this._ParamInfoCollection = new List<ITraceParamInfo>();
                }
                return true;
            }
            catch (Exception ex)
            {
                RealTimeTrace(ex);
                return false;
            }
        }

        private List<ITraceParamInfo> _ExtendedParamInfoCollection;
        /// <summary>
        /// Gets the collection of any additional parameters/variables/objects within the class in which the exception occured.
        /// </summary>
        public List<ITraceParamInfo> ExtendedParamInfoCollection
        {
            get { return this._ExtendedParamInfoCollection; }
        }

        /// <summary>
        /// Initialize the internal collection _ExtendedParamInfoCollection
        /// </summary>
        /// <returns>Returns true if execution of the method has succeeded.
        /// Returns false otherwise.</returns>
        private bool InitExtendedParamInfoCollection()
        {
            try
            {
                if (this._ExtendedParamInfoCollection == null)
                {
                    this._ExtendedParamInfoCollection = new List<ITraceParamInfo>();
                }
                return true;
            }
            catch (Exception ex)
            {
                RealTimeTrace(ex);
                return false;
            }
        }

        private string _ThreadID = System.String.Empty;
        /// <summary>
        /// Gets the thread ID in which the exception has occured.
        /// </summary>
        public string ThreadID
        {
            get { return this._ThreadID; }
        }

        private string _ExceptionStack = System.String.Empty;
        /// <summary>
        /// Gets or Sets the Execution Stack when the exception has been fired by the CLR.
        /// </summary>
        public string ExceptionStack
        {
            get { return this._ExceptionStack; }
        }

        private string _EnvironmentStack = System.String.Empty;
        /// <summary>
        /// Gets the Environment Stack when the exception has been fired by the CLR.
        /// </summary>
        public string EnvironmentStack
        {
            get { return this._EnvironmentStack; }
        }

        private string _ExceptionType = System.String.Empty;
        /// <summary>
        /// Gets the .NET type of the exception object raised by the CLR.
        /// </summary>
        public string ExceptionType
        {
            get { return this._ExceptionType; }
        }

        private string _Message = System.String.Empty;
        /// <summary>
        /// Gets the error message.
        /// </summary>
        public string Message
        {
            get { return this._Message; }
        }

        private string _Culture = System.String.Empty;
        /// <summary>
        /// Gets the Culture of the thread in which the exception has occured.
        /// </summary>
        public string Culture
        {
            get { return this._Culture; }
        }

        private string _UICulture = System.String.Empty;
        /// <summary>
        /// Gets the UICulture of the thread in which the exception has occured.
        /// </summary>
        public string UICulture
        {
            get { return this._UICulture; }
        }

        private System.DateTime _Date = System.DateTime.Now;
        /// <summary>
        /// Gets the Date and Time at which the exception has occured.
        /// </summary>
        public DateTime Date
        {
            get { return this._Date; }
        }

        private string _ClassDump = System.String.Empty;
        /// <summary>
        /// Gets or Sets a serialized version of the object in which the exception has been thrown.
        /// </summary>
        public string ClassDump
        {
            get
            {
                return this._ClassDump;
            }
            set
            {
                if (System.String.IsNullOrEmpty(value))
                {
                    return;
                }
                this._ClassDump = value;
            }
        }

        #endregion

        #region Thread ID extraction

        private bool GetThreadID()
        {
            try
            {
                this._ThreadID = System.Threading.Thread.CurrentThread.ManagedThreadId.ToString();
                return true;
            }
            catch (Exception ex)
            {
                RealTimeTrace(ex);
                return false;
            }
        }

        #endregion

        #region Culture extraction
        private bool GetCulture()
        {
            try
            {
                this._Culture = System.Threading.Thread.CurrentThread.CurrentCulture.ToString();
                return true;
            }
            catch (Exception ex)
            {
                RealTimeTrace(ex);
                return false;
            }
        }

        private bool GetUICulture()
        {
            try
            {
                this._UICulture = System.Threading.Thread.CurrentThread.CurrentUICulture.ToString();
                return true;
            }
            catch (Exception ex)
            {
                RealTimeTrace(ex);
                return false;
            }
        }
        #endregion

        #region Environment stack extraction

        /// <summary>
        /// Checks if the current executing thread has the permission to access the System.Environment properties.
        /// </summary>
        /// <returns>Returns true if permission is allowed.
        /// Returns false if permission is denied.</returns>
        private bool CanGetEnvironmentStack()
        {
            try
            {
                System.Security.Permissions.EnvironmentPermission myPermission =
                    new System.Security.Permissions.EnvironmentPermission(System.Security.Permissions.PermissionState.Unrestricted);
                myPermission.Demand();

                return true;
            }
            catch (Exception ex)
            {
                this._EnvironmentStack = ex.Message;
                RealTimeTrace(ex);
                return false;
            }
        }

        /// <summary>
        /// Sets the ExceptionStack property.
        /// </summary>
        /// <returns>Returns true if execution of the method has succeeded.
        /// Returns false otherwise.</returns>
        private bool GetEnvironmentStack()
        {
            try
            {
                if (! this.CanGetEnvironmentStack())
                {
                    return false;
                }

               // now we are sure to securely access the Environment object
                this._EnvironmentStack = System.Environment.StackTrace;

                return true;
            }
            catch (Exception ex)
            {
                this._EnvironmentStack = ex.Message;
                RealTimeTrace(ex);
                return false;
            }
        }

        #endregion

        #region Infos extraction from any Exception context

        /// <summary>
        /// Extracts any infos available outside of the Exception object.
        /// </summary>
        /// <returns>Returns true if execution of the method has succeeded.
        /// Returns false otherwise.</returns>
        private bool GetInfos()
        {
            try
            {
                if (! this.GetEnvironmentStack())
                {
                    // TODO : think of it for the next release
                }

                if (!this.GetThreadID())
                {
                    // TODO : think of it for the next release
                }

                if (!this.GetCulture())
                {
                    // TODO : think of it for the next release
                }

                if (!this.GetUICulture())
                {
                    // TODO : think of it for the next release
                }

                return true;
            }
            catch (Exception ex)
            {
                RealTimeTrace(ex);
                return false;
            }
        }

        /// <summary>
        /// Extracts any infos available inside and outside of the Exception object.
        /// </summary>
        /// <param name="ex0">Exception object raised by the caller of the RealtimeTrace framework.</param>
        /// <returns>Returns true if execution of the method has succeeded.
        /// Returns false otherwise.</returns>
        private bool GetInfos(System.Exception ex0)
        {
            try
            {
                // Extract infos avalaible outside the exception object.
                if (!this.GetInfos())
                {
                    // TODO : think of it for the next release
                }

                // Check input parameters
                if (ex0 == null)
                {
                    throw new System.ArgumentNullException();
                }
                
                this._ClassName = ex0.TargetSite.ReflectedType.FullName;
                this._MethodName = ex0.TargetSite.Name;
                this._Message = ex0.Message;
                this._ExceptionStack = ex0.StackTrace.ToString();
                this._ExceptionType = ex0.GetType().ToString();

                if (! this.BuildParamInfoCollection(ex0))
                {
                 return false;   
                }


                return true;
            }
            catch (Exception ex)
            {
                RealTimeTrace(ex,ex0);
                return false;
            }
        }

        /// <summary>
        /// Extracts any infos available inside and outside of the Exception object.
        /// </summary>
        /// <param name="ex0">Exception object raised by the caller of the RealtimeTrace framework.</param>
        /// <param name="args">List of values for each parameter of the method in which the exception has been raised.</param>
        /// <returns>Returns true if execution of the method has succeeded.
        /// Returns false otherwise.</returns>
        private bool GetInfos(System.Exception ex0, params object[] args)
        {
            try
            {
                // Extract infos avalaible outside the exception object.
                if (!this.GetInfos())
                {
                    // TODO : think of it for the next release
                }

                // Check input parameters
                if (ex0 == null)
                {
                    throw new System.ArgumentNullException();
                }

                this._ClassName = ex0.TargetSite.ReflectedType.FullName;
                this._MethodName = ex0.TargetSite.Name;
                this._Message = ex0.Message;
                this._ExceptionStack = ex0.StackTrace.ToString();
                this._ExceptionType = ex0.GetType().ToString();

                if (!this.BuildParamInfoCollection(ex0,args))
                {
                    return false;
                }

                return true;
            }
            catch (Exception ex)
            {
                RealTimeTrace(ex, ex0);
                return false;
            }
        }

        /// <summary>
        /// Extract all parameters info from the exception into TraceParamInfo objects. 
        /// Then store each TraceParamInfo in the internal collection.
        /// </summary>
        /// <param name="ex0">Exception object raised by the caller of the RealtimeTrace framework.</param>
        /// <returns></returns>
        private bool BuildParamInfoCollection(System.Exception ex0)
        {
            try
            {
                // Check input parameters
                if (ex0 == null)
                {
                    throw new System.ArgumentNullException();
                }

                // Check if the internal collection has been initialized
                if (! this.InitParamInfoCollection())
                {
                    return false;
                }

                // Get the parameters of the method in which the exception has been raised.
                System.Reflection.ParameterInfo[] myParamInfos = ex0.TargetSite.GetParameters();

                // Check if the collection of parameters is a valid object
                if (myParamInfos == null)
                {
                    return true;
                }

                // Check if the collection of parameters is empty
                if (myParamInfos.Length == 0)
                {
                    return true;
                }

                foreach (System.Reflection.ParameterInfo item in myParamInfos)
                {
                    TraceParamInfo myParamInfo = new TraceParamInfo(item);
                    this.ParamInfoCollection.Add(myParamInfo);
                }

                return true;
            }
            catch (Exception ex)
            {
                RealTimeTrace(ex,ex0);
                return false;
            }
        }

        /// <summary>
        /// Extract all parameters info from the exception into TraceParamInfo objects. 
        /// Then store each TraceParamInfo in the internal collection.
        /// </summary>
        /// <param name="ex0">Exception object raised by the caller of the RealtimeTrace framework.</param>
        /// <param name="args">List of values for each parameter of the method in which the exception has been raised.</param>
        /// <returns>Returns true if execution of the method has succeeded.
        /// Returns false otherwise.</returns>
        private bool BuildParamInfoCollection(System.Exception ex0, params object[] args)
        {
            try
            {
                // Check input parameters
                if (ex0 == null)
                {
                    throw new System.ArgumentNullException();
                }

                if (args == null)
                {
                    throw new System.ArgumentNullException();
                }

                // Check if the internal collection has been initialized
                if (!this.InitParamInfoCollection())
                {
                    return false;
                }

                // Get the parameters of the method in which the exception has been raised.
                System.Reflection.ParameterInfo[] myParamInfos = ex0.TargetSite.GetParameters();

                // Check if the collection of parameters is a valid object
                if (myParamInfos == null)
                {
                    return true;
                }

                // Check if the collection of parameters is empty
                if (myParamInfos.Length == 0)
                {
                    return true;
                }

                // Setup an index to access items in the args array.
                int myIndex = 0;

                foreach (System.Reflection.ParameterInfo item in myParamInfos)
                {
                    // Check if we have a valid instance for this parameter
                    if (myIndex >= args.Length)
                    {
                        // the caller has not correctly set the code to call the method RealTimeTrace(ex, param1, param2, etc...)
                        // Argument(s) are missing ...
                        TraceParamInfo myPartialParamInfo = new TraceParamInfo(item);
                        this.ParamInfoCollection.Add(myPartialParamInfo);
                        myIndex += 1;
                        continue;
                    }

                    // We have a valid instance for this parameter
                    TraceParamInfo myParamInfo = new TraceParamInfo(item,args[myIndex]);
                    this.ParamInfoCollection.Add(myParamInfo);
                    myIndex += 1;
                }

                // Check if there are more items in the args array that have not been enumerated
                if (myIndex >= args.Length)
                {
                    // There are no more objects to enumerate in the params array
                    return true;
                }

                // We have additional objects to enumerate in the params array
                // additionals objects must be like a collection of pair : variableName, VariableValue
                for (int i = myIndex; i <= (args.Length-2); i++)
                {
                    if (args[i] == null)
                    {
                        continue;
                    }
                    // Get the pair : name, value;
                    string myObjectName = args[i].ToString();
                    System.Object myObjectValue = args[i + 1];
                    i += 1;
                    if (! this.AddExtendedParamInfo(myObjectName,myObjectValue))
                    {
                        // TODO : think of it for the next release
                    }

                } // for

                return true;
            }
            catch (Exception ex)
            {
                RealTimeTrace(ex, ex0);
                return false;
            }
        }

        /// <summary>
        /// Adds a new TraceParamInfo object based on the parameter's name and value.
        /// Then stores this parameter object in the internal ExtendedParamInfoCollection.
        /// </summary>
        /// <param name="paramName">The name of the parameter.</param>
        /// <param name="value">The value of the parameter.</param>
        /// <returns>Returns true if execution of the method has succeeded.
        /// Returns false otherwise.</returns>
        private bool AddExtendedParamInfo(string paramName, object value)
        {
            try
            {
                // Check if the internal collection has been initialized
                if (! this.InitExtendedParamInfoCollection())
                {
                    return false;
                }

                TraceParamInfo myParamInfo = new TraceParamInfo(paramName, value);
                this.ExtendedParamInfoCollection.Add(myParamInfo);
                return true;
            }
            catch (Exception ex)
            {
                RealTimeTrace(ex, paramName,value);
                return false;
            }
        }


#endregion

        #region Constructors

        /// <summary>
        /// Default constructor is hidden to force the caller to pass an Exception object.
        /// </summary>
        private TraceInfo()
        {
        }

        /// <summary>
        /// Class constructor.
        /// </summary>
        /// <param name="ex0">Exception object raised by the caller of the RealtimeTrace framework.</param>
        /// <param name="count">Instance counter</param>
        internal TraceInfo(System.Exception ex0, System.Int32 count)
        {
            try
            {
                this._Count = count;

                if (! this.GetInfos(ex0))
                {
                    // TODO : think of it for the next release
                }

            }
            catch (Exception ex)
            {
                RealTimeTrace(ex, ex0);
            }
        }

        /// <summary>
        /// Class constructor.
        /// </summary>
        /// <param name="ex0">Exception object raised by the caller of the RealtimeTrace framework.</param>
        /// <param name="count">Instance counter</param>
        /// <param name="args">List of values for each parameter of the method in which the exception has been raised.</param>
        internal TraceInfo(System.Exception ex0,System.Int32 count, params object[] args)
        {
            try
            {
                this._Count = count;

                if (! this.GetInfos(ex0,args))
                {
                    // TODO : think of it for the next release
                }
            }
            catch (Exception ex)
            {
                RealTimeTrace(ex, ex0);
            }
        }

        #endregion

    }
}
